#include "stdafx.h"
#include "ResourceInstance.h"
#include "MGCfg.h"
#include "MGActionImpl.h"
#include "MSDevApp.h"
#include "KeybdEvent.h"
#include "resource.h"
#include "TUtils.h"

#define AUTO_ACTION_FLAG(bActionFlag) CMGActionImpl::XAutoActionFlag __AutoActionFlag__(##bActionFlag)

/*
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Shared variables among processes to apply options altogether
///////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma data_seg("SharedVariable")

  #pragma data_seg()
  
	#pragma comment(linker, "/SECTION:SharedVariable,S")
	///////////////////////////////////////////////////////////////////////////////////////////////////////
	// End of Shared variables
	///////////////////////////////////////////////////////////////////////////////////////////////////////
*/

LPCTSTR CMGActionImpl::szValidWindowClasses[] = 
{
	_T("CGridControlWnd"),
		_T("CSlobDragListBox"),
		_T("CDlgWnd"),
		_T("CImgWnd"),
		_T("CTradBar"),
		_T("CMenuFrame"),
		_T("CBinaryView"),
		_T("CTextView"),
		_T("COutputView"),
		_T("CWorkspacePaneWnd"),
		_T("CDAMView"),
		//	_T("CSushiBar"),				// MS Developer Studio 6's Status Bar
		//	_T("CASBar"),					// MS Developer Studio 6's Menu Bar
};

CMGActionImpl::CMGActionImpl()
	: m_hMainWnd(NULL), m_hCursorA(NULL), m_hCursorB(NULL), m_hCursorPrev(NULL), m_hStatusBar(NULL),
	m_iValidWindowClass(0), m_dwThreadID(0), m_hIcon(NULL), m_bIdentifyTarget(FALSE), m_bMGInAction(FALSE)
{
};

CMGActionImpl::~CMGActionImpl()
{
}


void CMGActionImpl::UpdateRegistry(BOOL /*bSaveAndValidate*/ /* = TRUE*/)
{
}

int CMGActionImpl::CheckTargetValid(HWND hWnd)
{
	CWnd *pWnd = CWnd::FromHandle(hWnd);
	if (pWnd)
	{
		const CRuntimeClass *pClass = pWnd->GetRuntimeClass();
		
		int nValidWindowClasses = sizeof(szValidWindowClasses) / sizeof(szValidWindowClasses[0]);
		for (int i = 0; i < nValidWindowClasses; i++)
		{
			
			UINT iIndex = (m_iValidWindowClass + i) % nValidWindowClasses;
			if ( (::_tcslen(szValidWindowClasses[iIndex]) == ::_tcslen( CString(pClass->m_lpszClassName) ) ) && !::_tcscmp( szValidWindowClasses[iIndex], CString(pClass->m_lpszClassName) ) )
			{
				TRACE(_T("Valid target window (%s)\n"), CString(pClass->m_lpszClassName));
				
				DWORD dwThreadID = ::GetCurrentThreadId();
				if(dwThreadID != m_dwThreadID)
				{
					m_dwThreadID = dwThreadID;
					m_hMainWnd = NULL;
					m_hStatusBar = NULL;
				}
				
				return (m_iValidWindowClass = iIndex);
			}
		}
	}
	
	return -1;
}

void CMGActionImpl::Cleanup()
{
}

BOOL CMGActionImpl::ExecuteDblClk(HWND hWnd, enum MG::DoubleClickOption option, const POINT& /*ptTarget*/, BYTE bCatTarget/* = 0*/)
{
	if( 7 == bCatTarget || 10 == bCatTarget )
	{
		// CMGActionImpl::szValidWindowClasses[7] == _T("CTextView")
		// CMGActionImpl::szValidWindowClasses[10] == _T("CDAMView"),
		
		DWORD dwRes = 0;
		switch(option)
		{
		case MG::DOUBLE_CLICK_PAGE_TOP:
			/// simulate CTRL(VK_CONTROL, 0x1D) + HOME(VK_HOME, 0x47) key
			CKeybdEvent::Combine2Strokes(hWnd, VK_CONTROL, VK_HOME);
			break;
			
		case MG::DOUBLE_CLICK_PAGE_BOTTOM:
			/// simulate CTRL(VK_CONTROL, 0x1D) + END(VK_END, 0x4F) key
			CKeybdEvent::Combine2Strokes(hWnd, VK_CONTROL, VK_END);
			break;
			
		case MG::DOUBLE_CLICK_PAGE_UP:
			/// simulate PAGE UP(VK_PRIOR, 0x49) key
			CKeybdEvent::SingleStroke(hWnd, VK_PRIOR);
			break;
			
		case MG::DOUBLE_CLICK_PAGE_DOWN:
			/// simulate PAGE DOWN(VK_NEXT, 0x51) key
			CKeybdEvent::SingleStroke(hWnd, VK_NEXT);
			break;
			
		case MG::DOUBLE_CLICK_NONE:
			return FALSE;
			break;
		}
		
		return TRUE;
	}
	
	return FALSE;
}

HWND CMGActionImpl::GetMainWindow(HWND hActiveWnd)
{
	if(m_hMainWnd == NULL)
	{
		HWND hWnd = hActiveWnd
			? hActiveWnd
			: ::GetActiveWindow();
		
		HWND hDesktopWnd = ::GetDesktopWindow();
		
		while( hWnd && hWnd != hDesktopWnd )
		{
			m_hMainWnd = hWnd;
			hWnd = ::GetParent(hWnd);
		}
	}
	
	return m_hMainWnd;
}

void CMGActionImpl::ExecuteCommand(HWND hWnd, WORD wID1, ...)
{
	HWND hMainWnd = GetMainWindow(hWnd);
	
	WORD wID = wID1;
	va_list marker;
	va_start(marker, wID1);	/// initialize variable arguments
	
	while((WORD)-1 != wID)
	{
		::PostMessage(hMainWnd, WM_COMMAND, (WPARAM)wID, (LPARAM)0);
		wID = va_arg(marker, WORD);
	}
	
	va_end(marker);				/// reset variable arguments
}

void CMGActionImpl::ExecuteCommand(LPCTSTR pszID1, ... )
{
	IApplication *pIApp = (IApplication *)CMSDevApp::Instance();
	if(pIApp)
	{
		USES_CONVERSION;
		
		//	pIApp->AddRef();		/// we know the life time of MS DEV explicitly
		
		LPCTSTR pszID = pszID1;
		va_list marker;
		va_start(marker, pszID1);	/// initialize variable arguments
		
		while(pszID)
		{
			BSTR sID = ::SysAllocString(T2COLE(pszID));
			pIApp->ExecuteCommand(sID);
			::SysFreeString(sID);
			
			pszID = va_arg(marker, LPCTSTR);
		}
		
		va_end(marker);				/// reset variable arguments
		
		//	pIApp->Release();		/// we know the life time of MS DEV explicitly
	}
}

BOOL CMGActionImpl::NoCommand(HWND hWnd, BOOL bRightButton, const POINT &ptTarget, BYTE /*bCatTarget*/ /* = 0*/)
{
	if( bRightButton && MGCfg::Get().Option().IsPreemptiveMGAButtonAlways() )
		DisplayContextMenu(hWnd, ptTarget);
	
	return TRUE;
}

BOOL CMGActionImpl::DisplayContextMenu(HWND hWnd, const POINT &/*ptTarget*/)
{
	POINT ptScreen = { 0 };
	::GetCursorPos(&ptScreen);
	::PostMessage(hWnd, WM_CONTEXTMENU, (WPARAM)hWnd, MAKELONG((WORD)ptScreen.x, (WORD)ptScreen.y));
	
	return TRUE;
}

BOOL CMGActionImpl::ExecuteCommand(HWND hWnd, WORD wID, LPCTSTR pszID, const POINT& /*ptTarget*/, BYTE /*bCatID*/ /* = 0*/,BYTE /*bCatTarget*/ /* = 0*/)
{
	AUTO_ACTION_FLAG(m_bMGInAction);
	
	switch(wID)
	{
	case 35087:	// WBGoToNext
	case 35088:	// WBGoToPrevious
		ExecuteCommand(hWnd, wID, 34867, (WORD)-1);	// WindowScrollToCenter(34867), (WORD)-1 is used as command terminator
		return TRUE;
		break;
		
	case (WORD)-2:
		ExecuteCommand(pszID, NULL);	// NULL is used as command terminator
		return TRUE;
		
		break;
	}
	
	ExecuteCommand(hWnd, wID, (WORD)-1);	// (WORD)-1 is used as command terminator
	
	return TRUE;
}

#define DEF_MESSAGE		_T("Ready")

void CMGActionImpl::Notify(enum MG::NotifyOption notification)
{
	if (NULL == CMGActionImpl::m_hCursorA)
	{
		m_hCursorA = ::LoadCursor(NULL, IDC_APPSTARTING);
		m_hCursorB = ::LoadCursor(NULL, IDC_ARROW);
	}
	
	if(NULL == m_hStatusBar)
		m_hStatusBar = FindStatusBarHandle();
	
	switch(notification)
	{
	case MG::MG_ACTIVATE:
		{
			m_hCursorPrev = ::SetCursor(m_hCursorA);
		}
		break;
		
	case MG::MG_DEACTIVATE:
		{
			::SetCursor(m_hCursorPrev);
		}
		break;
		
	case MG::MG_ERROR:
		{
			enum { BUF_SIZE = 256 };
			TCHAR sBuf[BUF_SIZE];
			::LoadString(CResourceInstance::Get(), IDS_E_GESTURE_ERROR, sBuf, BUF_SIZE);
			UpdateStatusBar( sBuf );
		}
		break;
		
	case MG::MGAB_PREEMPTIVE_ON:
		{
			enum { BUF_SIZE = 256 };
			TCHAR sBuf[BUF_SIZE];
			for (int i = 0; i < 2; i++)
			{
				::SetCursor(m_hCursorB);
				UpdateStatusBar(NULL);
				::Sleep(50);
				::SetCursor(m_hCursorA);
				
				::LoadString(CResourceInstance::Get(), IDS_MG_PREEMPTIVE_ON, sBuf, BUF_SIZE);
				UpdateStatusBar( sBuf );
				::Sleep(100);
			}
			
			::SetCursor(m_hCursorPrev);
		}
		break;
		
	case MG::MGAB_PREEMPTIVE_OFF:
		{
			enum { BUF_SIZE = 256 };
			TCHAR sBuf[BUF_SIZE];
			::SetCursor(m_hCursorB);
			UpdateStatusBar(NULL);
			::Sleep(50);
			::SetCursor(m_hCursorA);
			::LoadString(CResourceInstance::Get(), IDS_MG_PREEMPTIVE_OFF, sBuf, BUF_SIZE);
			UpdateStatusBar( sBuf );
			::Sleep(100);
			
			::SetCursor(m_hCursorPrev);
		}
	}
}

void CMGActionImpl::UpdateStatusBarWheelCommand(LPCTSTR pszCommand, BOOL bUp)
{
	std::tostringstream tssMsg;
	
	enum { BUF_SIZE = 256 };
	TCHAR sBuf[BUF_SIZE];
	::LoadString(CResourceInstance::Get(), IDS_MG_WHEEL_COMMAND, sBuf, BUF_SIZE);
	tssMsg << sBuf;
	::LoadString(CResourceInstance::Get(), bUp ? IDS_MG_WHEEL_UP : IDS_MG_WHEEL_DOWN, sBuf, BUF_SIZE);
	tssMsg << _T(" ") << sBuf << _T(" ") << pszCommand;
	
	UpdateStatusBar( tssMsg.str().c_str() );
}

/**
*	CMGActionImpl::UpdateStatusBar()
*
*	excerpt from AfxSetWindowText() implementation in WinUtil.cpp
*
*	@param LPCTSTR pszMessage
*
*	@return void
*
*/
void CMGActionImpl::UpdateStatusBar(LPCTSTR pszMessage)
{
	if (m_hStatusBar == NULL)
		m_hStatusBar = FindStatusBarHandle();
	
	if (m_hStatusBar && !::IsWindow(m_hStatusBar))
	{
		m_hStatusBar = NULL;
		return;
	}
	
	if (pszMessage)
	{
		int nNewLen = ::lstrlen( pszMessage );
		TCHAR szOld[256];
		// fast check to see if text really changes (reduces flash in controls)
		if (nNewLen > (sizeof(szOld)/sizeof(szOld[0])) ||
			::GetWindowText(m_hStatusBar, szOld, (sizeof(szOld)/sizeof(szOld[0]))) != nNewLen || ::lstrcmp(szOld, pszMessage ) != 0)
		{
			// change it
			::SetWindowText( m_hStatusBar, pszMessage );
		}
	}
	else
	{
		static LPCTSTR szDefault = DEF_MESSAGE;
		::SetWindowText(m_hStatusBar, szDefault);
	}
}

#define DEF_STATUSBAR_CID	0x0000E801

/**
*	CMGActionImpl::FindStatusBarHandle()
*
*
*	@return HWND
*
*/
HWND CMGActionImpl::FindStatusBarHandle()
{
	HWND hMainWnd = GetMainWindow();
	
	m_hStatusBar = ::GetDlgItem(hMainWnd, DEF_STATUSBAR_CID);
	
	return m_hStatusBar;
}

void CMGActionImpl::IdentifyTargetWindowClass(HWND hWnd)
{
	m_bIdentifyTarget = TRUE;
	
	std::tostringstream tss;
	
	enum { BUFFER_SIZE = 128 };
	TCHAR pszClassName[BUFFER_SIZE];
	::GetClassName(hWnd, pszClassName, BUFFER_SIZE);
	
	tss << pszClassName;
	
	tss << _T(",(") << std::hex << hWnd << _T(")");
	
	UpdateStatusBar( tss.str().c_str() );
}
